﻿/*********************************************************
* RAJON
* REST AJAX JSON OVER NETWORK
* 
* Marcos D.A. Martins
*********************************************************/


//#region JQUERY EXTENSIONS //////////////////////////////////////////////////
jQuery.fn.outerHTML = function () {
    return $('<div>').append(this.eq(0).clone()).html();
};
//#endregion


//#region BASIC CONVERTERS //////////////////////////////////////////////////
function DateConverter() {};
DateConverter.convert = function (value) {
    return Rajon.formatDate(value);
}
DateConverter.convertBack = function (value) {
    return Rajon.toDate(value);
}
DateConverter.convertForQueryString = function (value) {
    if (value == '')
        return '01/01/0001'; // this will work, only for WCF i guess
    else
        return Rajon.toQueryStringDate(value);
}
        
function IntegerConverter() {};
IntegerConverter.convert = function (value) {
    return value;
}
IntegerConverter.convertBack = function (value) {
    return parseInt(value);
}

function FloatConverter() {};
FloatConverter.convert = function (value) {
    return value.toLocaleString();
}
FloatConverter.convertBack = function (value) {
    return Rajon.toFloat(value);
}

//#endregion


function RajonViewModel(model) {

    this.model = model;
    if (this.model == null)
        this.model = new Object();
    this.newModel = null;
    this.gettingModel = false;

    //#region VIEW METHODS //////////////////////////////////////////////////

    var _view = document.createElement("div");

    this.getView = function () {
        /// <summary>Gets the view of this ViewModel</summary>
        /// <returns>The HTML element</returns>
        return _view;
    }

    this.setView = function (view) {
        /// <summary>Sets the view of this ViewModel</summary>
        _view = view;
        view.vmodel = this;
    }

    //#endregion

    //#region PAGINING METHODS //////////////////////////////////////////////////

    var _pgNumber = 1;
    this.itemsTotalCount = 0;
    this.pageSize = 0;
    this.pageScrollSpeed = 0;

    this.getPageNumber = function () {
        return _pgNumber;
    }

    this.getPageCount = function () {
        var pgCount = this.itemsTotalCount / this.pageSize;
        if (this.itemsTotalCount % this.pageSize != 0)
            pgCount++;
        return Math.floor(pgCount);
    }

    this.bindNextPage = function () {

        if (this.pageCount > 0 && _pgNumber >= this.pageCount)
            return;

        _pgNumber++;

        // if no scroll animaton is needed
        if (isNaN(this.pageScrollSpeed))
            this.pageScrollSpeed = 0;

        this.getModelFromServer(
        function (me) {
            for (var i = 0, li = me.newModel.length; i < li; i++) {
                var itemvModel = me.addItemViewModel(me.newModel[i]);
                itemvModel.bind();
            }
            var l = me.newModel.length;
            me.newModel = null;
            var view = me.getView();
            // ONLY FF support scroll on TBODY
            if (me.pageScrollSpeed == 0 || (view.nodeName == 'TBODY' && navigator.userAgent.indexOf("Firefox") == -1)) {
                for (var i = l - 1; i >= 0; i--) me.removeItemViewModel(me.model[i]);
                view.scrollTop = view.scrollHeight;
            }
            else {
                $(view).animate({ scrollTop: view.scrollHeight }, me.pageScrollSpeed,
                    function () {
                        for (var i = l - 1; i >= 0; i--) me.removeItemViewModel(me.model[i]);
                        view.scrollTop = view.scrollHeight; // need that for FF
                    });
            }
        });

    }

    this.bindPreviousPage = function () {
        if (_pgNumber == 1)
            return;
        _pgNumber--;

        // if no scroll animaton is needed
        if (isNaN(this.pageScrollSpeed))
            this.pageScrollSpeed = 0;


        this.getModelFromServer(
        function (me, newItems) {
            var view = me.getView();
            var oldItemsLength = me.model.length;
            for (var i = me.newModel.length - 1; i >= 0; i--) {
                var itemvModel = me.addItemViewModel(me.newModel[i], 'top');
                itemvModel.bind();
            }
            view.scrollTop = view.scrollHeight;
            // ONLY FF support scroll on TBODY
            if (me.pageScrollSpeed == 0 || (view.nodeName == 'TBODY' && navigator.userAgent.indexOf("Firefox") == -1)) {
                for (var i = oldItemsLength - 1; i >= 0; i--) me.removeItemViewModel(me.model[i]);
            }
            else {
                $(view).animate({ scrollTop: 0 }, me.pageScrollSpeed,
                    function () {
                        for (var i = oldItemsLength - 1; i >= 0; i--) me.removeItemViewModel(me.model[i]);
                    });
            }
        });

    }

    this.bindToPage = function (pg) {
        _pgNumber = pg;
        if (this.model instanceof Array) {
            this.clearItemsViewModel();
            this.model = null;
        }
        this.bind();
    }

    this.bindToFirstPage = function (pg) {
        this.bindToPage(1);
    }

    this.bindToLastPage = function (pg) {
        this.bindToPage(this.getPageCount());
    }

    //#endregion

    //#region BIND METHODS //////////////////////////////////////////////////

    this.isSERVERDataContext = function () {
        if (this.datacontext == null)
            return false;
        return (this.datacontext.substr(0, 1) != '=' && this.datacontext.substr(0, 1) != '>');
    }

    this.isOBJECTDataContext = function () {
        if (this.datacontext == null)
            return false;
        return (this.datacontext.substr(0, 1) == '=');
    }

    this.isLINKDataContext = function () {
        if (this.datacontext == null)
            return false;
        return (this.datacontext.substr(0, 1) == '>');
    }



    this.bind = function (onModelRetrived) {

        var myView = this.getView();
        if (myView == null) {
            Rajon.log('Rajon issue: Can not bind to a ViewModel without View.');
            return;
        }

        if (this.isSERVERDataContext())
            this.getModelFromServer(function (me) {
                if (onModelRetrived != null)
                    onModelRetrived(me);
                else if (this.onModelRetrived != null)
                    this.onModelRetrived(me);

                me.refresh(true);
            });
        else
            this.refresh();
    }

    defaultTagAttribute = function (tag) {
        if ($(tag).is('input') || $(tag).is('select'))
            return 'value';
        if ($(tag).is('img'))
            return 'src';
        if ($(tag).is('option'))
            return 'text';
        if ($(tag).is('a'))
            return 'href';
        return 'innerHTML';
    }



    getModelFromObject = function (me) {
        me.newModel = null;
        try {
            eval('me.newModel = ' + me.datacontext.substr(1));
        }
        catch (e) {
            Rajon.log('Invalid model source at view \'' + me.getView().id + '\'.\'');
        }
    }


    linkedViewModel = function (me) {

        var id = me.datacontext.substr(1);

        // PARENT LINK: ex: >.model.property or >.vmodelMethod
        if (id.indexOf('.') == 0) {
            var myView = me.getView();
            var parentViewModel = Rajon.getParentViewModel(myView);
            if (parentViewModel == null) {
                Rajon.log('Rajon issue: could not find parent to link bind.');
                return null;
            }
            id = parentViewModel.getView().id;
        }
        // OTHER VIEW LINK: >viewId.model.property or >viewId.vmodelMethod
        else if (id.indexOf('.') > 0) {
            id = me.datacontext.substr(1, me.datacontext.indexOf('.') - 1);
        }

        // the otherview to get the model from
        var otherView = document.getElementById(id);
        if (otherView == null) {
            Rajon.log('Rajon issue: could not find ' + id + ' view to link bind.');
            return null;
        }

        // the other viewmodel
        var otherVModel = otherView.vmodel;
        if (otherVModel == null) {
            Rajon.log('Rajon issue: could not find ' + id + ' .vmodel to link bind.');
            return null;
        }

        return otherVModel;
    }

    getModelFromOtherView = function (me) {

        me.newModel = null;

        // gets the linked viewmodel
        var othervModel = linkedViewModel(me);
        if (othervModel == null)
            return true;

        // may happen that the linked viewmodel, is linked to another one and so on
        // so, goes up until find a OBJECT or REST model
        var orgViewModel = othervModel;
        while (orgViewModel.isLINKDataContext())
            orgViewModel = linkedViewModel(orgViewModel);

        // if the orginal model is getting the model, try later
        if (orgViewModel.gettingModel) {
            window.setTimeout(function () { me.refresh(); }, 10);
            return false;
        }

        var id = me.datacontext.substr(1);
        var props = '';

        // PARENT LINK: ex: >.model.property or >.vmodelMethod
        if (id.indexOf('.') == 0) {
            props = me.datacontext.substr(1);
        }
        // OTHER VIEW LINK: >viewId.model.property or >viewId.vmodelMethod
        else if (id.indexOf('.') > 0) {
            props = me.datacontext.substr(me.datacontext.indexOf('.'));
        }

        try {
            me.newModel = eval('othervModel' + props);
        } catch (e) {
            Rajon.log('Rajon issue: Could not bind ' + props);
        }
        return true;
    }


    this.refresh = function (forceBind) {

        if (forceBind == null)
            forceBind = false;

        // if is a REST source and is getting the model, try later
        if (this.gettingModel) {
            window.setTimeout(function () { me.refresh(forceBind); }, 10);
            return;
        }

        // if is an OBJECT source, get the model now
        if (this.isOBJECTDataContext())
            getModelFromObject(this);

        // otherwise, get it now from the linked source
        if (this.isLINKDataContext()) {
            if (!getModelFromOtherView(this))
                return;
        }

        // no model was passed, uses the model
        if (this.newModel == null)
            this.newModel = this.model;

        if (this.model != this.newModel)
            onModelSourceChange(this, this.newModel);

        if (this.newModel == null)
            return;


        if (!(this.newModel instanceof Array)) {
            this.model = this.newModel;
            bindEachViewTag(this);
        }
        else {
            // binds the model item by item            
            for (var i = 0, li = this.newModel.length; i < li; i++) {

                // verify if item already has a view
                var itemvModel = this.getItemViewModel(this.newModel[i]);
                if (itemvModel == null)
                    itemvModel = this.addItemViewModel(this.newModel[i]);

                itemvModel.bind();
            }
        }

        this.newModel = null;

        parentBindEachViewTag(this);

        // bind child-view models
        var childViews = new Array();
        Rajon.getFromView(this.getView(), 'rajon:datacontext', childViews);
        for (var i = 0, li = childViews.length; i < li; i++) {
            if (childViews[i].vmodel == null)
                Rajon.createViewModel(childViews[i]);

            // THE PROBLEM GOES HERE, SHOULD I CALL cmodel.bind() or vmodel.refresh() ?
            if (childViews[i].vmodel.bindsOn == null || childViews[i].vmodel.bindsOn == 'onLoad') {
                if (forceBind)
                    childViews[i].vmodel.bind();
                else
                    childViews[i].vmodel.refresh(false);
            }

        }


    }

    bindTag = function (tag, isParentBind) {

        var bindExpr = null;
        if (!isParentBind)
            bindExpr = tag.getAttribute("rajon:bind");
        else
            bindExpr = tag.getAttribute("rajon:parentBind");

        if (bindExpr == null)
            return;

        var vmodel = Rajon.vmodel(tag);
        if (isParentBind)
            vmodel = Rajon.getParentViewModel(vmodel.getView());

        var exprList = bindExpr.split(',');
        for (var i = 0; i < exprList.length; i++)
            bindExpression(vmodel, tag, exprList[i]);


    }




    bindEachViewTag = function (me) {
        var view = me.getView();
        var rootElementBind = view.getAttribute('rajon:bind');
        if (rootElementBind != null)
            bindTag(view);

        var tags = new Array();
        Rajon.getFromView(view, 'rajon:bind', tags);
        for (var i = 0; i < tags.length; i++)
            bindTag(tags[i]);

    }

    parentBindEachViewTag = function (me) {

        var view = me.getView();
        var rootElementBind = view.getAttribute('rajon:parentBind');
        //var tags = $(view).find('[parentBind]');
        //var tags = $('[parentBind]', view).filter(function () { return $(this).parents('[datacontext]').length <= 1; });
        var tags = new Array();
        Rajon.getFromView(view, 'rajon:bind', tags);

        if (tags.length == 0 && rootElementBind == null)
            return;

        var pvModel = Rajon.vmodel(me.getView());

        if (rootElementBind != null)
            bindTag(view, true);

        for (var i = 0; i < tags.length; i++)
            bindTag(tags[i], true);
    }

    bindExpression = function (me, tag, bindExpr) {
        var view = me.getView();
        var b = bindExpr.split('=');
        var attr = null;
        var valueName = null;

        if (b.length == 1) {
            valueName = b[0].replace(/^\s+|\s+$/g, "");
            attr = defaultTagAttribute(tag);
        }
        else {
            attr = b[0].replace(/^\s+|\s+$/g, "");
            valueName = b[1].replace(/^\s+|\s+$/g, "");
        }

        // IF IS A STATIC BIND
        if (valueName[0] == '[') {
            var value = eval(valueName.replace('[', '').replace(']', ''));
            if (value == null) {
                value = valueName;
                Rajon.log('rajon issue: could not find resource \'' + valueName + '\'');
            }
            tag[attr] = value;
            return;
        }

        // remove casts
        var castSuffix = '';
        if (valueName.indexOf('#') > 0) {
            castSuffix = valueName.substr(valueName.indexOf('#'));
            valueName = valueName.replace(castSuffix, '');
        }

        // checks null
        var valueExpr = '""';
        var value = eval('me.' + valueName);

        try {
            
            value = Rajon.convert(castSuffix, value);                    

            if (attr == 'value')
                eval('$(tag).val(value)');
            else
                eval('tag.' + attr + ' = value');
        }
        catch (e) {
            Rajon.log('rajon issue: could not bind \'' + valueName + '\'');
        }

    }

    //#endregion

    //#region VALIDATION METHODS  //////////////////////////////////////////////////

    getCommitErrorDisplay = function (tag) {
        var displays = $('[rajon\\:displayCommitErrorFor="' + tag.id + '"]');
        if (displays.length == 0)
            return null;
        return displays[0];
    }

    showCommitError = function (tag) {
        var errorDisplay = getCommitErrorDisplay(tag);
        if (errorDisplay != null) {
            errorDisplay.innerHTML = tag.CommitError;
            errorDisplay.style.visibility = 'visible';
        }
    }

    hideCommitError = function (tag) {
        var errorDisplay = getCommitErrorDisplay(tag);
        if (errorDisplay != null) {
            errorDisplay.style.visibility = 'hidden';
        }
    }


    //#endregion

    //#region COMMIT METHODS  //////////////////////////////////////////////////

    this.commitTag = function (tag) {
        commitTag(tag, false);
        commitTag(tag, true);
    }


    commitTag = function (tag, isParentBind) {

        var bindExpr = null;
        if (!isParentBind)
            bindExpr = tag.getAttribute("rajon:bind");
        else
            bindExpr = tag.getAttribute("rajon:parentBind");

        if (bindExpr == null)
            return;

        var vmodel = Rajon.vmodel(tag);
        if (isParentBind)
            vmodel = Rajon.getParentViewModel(vmodel.getView());

        var exprList = bindExpr.split(',');
        for (var i = 0; i < exprList.length; i++)
            commitExpression(vmodel, tag, exprList[i]);

    }

    this.commit = function () {

        var view = this.getView();

        if (this.model == null)
            this.model = new Object();

        this.hasCommitErrors = false;

        if (view.getAttribute('rajon:bind') != null || view.getAttribute('rajon:parentBind') != null)
            this.commitTag(view);

        var tags = new Array();
        Rajon.getFromView(view, 'rajon:bind, rajon:parentBind', tags);

        for (var i = 0; i < tags.length; i++)
            this.commitTag(tags[i]);


    }


    commitExpression = function (me, tag, bindExpr) {
        var view = me.getView();

        var b = bindExpr.split('=');
        var attr = null;
        var valueName = null;

        if (b.length == 1) {
            attr = defaultTagAttribute(tag);
            valueName = b[0].replace(/^\s+|\s+$/g, "");
        }
        else {
            attr = b[0].replace(/^\s+|\s+$/g, "");
            valueName = b[1].replace(/^\s+|\s+$/g, "");
        }

        if (typeof (me[valueName.replace('()', '')]) == 'function')
            return;

        var castSuffix = valueName.substr(valueName.length - 2);        
        valueName = valueName.replace(castSuffix, '');
        
        // do this cuz of IE select.value bug        
        if (attr == 'value')
            value = Rajon.convertBack(castSuffix, $(tag).val());
        else
            value = Rajon.convertBack(castSuffix, eval('tag.' + attr));

        var propertyName = valueName.replace('model.', '');

        // exceutes onCommit<PROPERTY> method, if exists
        tag.CommitError = null;
        if (me['onCommit' + propertyName] != null) {

            try {
                eval('me.onCommit' + propertyName + '(value)');
                hideCommitError(tag);
            }
            catch (e) {
                tag.CommitError = e;
                me.hasCommitErrors = true;
                showCommitError(tag);
            }
        }

        if (tag.CommitError == null)
            eval('me.' + valueName + ' = value');
    }


    //#endregion

    //#region REST METHODS  //////////////////////////////////////////////////

    this.onModelRetrived = function (func) {
        $(this.getView()).bind('onModelRetrived', this, func);
    }

    this.updateModelSource = function (args) {

        if (args == null)
            args = new Object();

        var onModelUpdated = args['onModelUpdated'];
        var onError = args['onError'];
        var datacontext = _parsedModelSrc(this, args['datacontext']);

        if (datacontext == null) {
            datacontext = this.parsedModelSrc();
            if (datacontext == null)
                throw 'Could not found model source for update model.';
        }

        // commit changes to the model
        if (this.getView() != null)
            this.commit();

        if (this.hasCommitErrors)
            return;

        if (this.model == null)
            return;

        var jsonStr = null;
        if (Rajon.wcfTarget)
            jsonStr = JSON.stringifyWCF(this.model);
        else
            jsonStr = JSON.stringify(this.model);

        var oldstatus = window.status;
        window.status = 'updating data...';

        var me = this;

        $.ajax({
            type: "PUT",
            url: datacontext,
            data: jsonStr,
            contentType: "application/json; charset=utf-8", dataType: "text",
            error:
                function (e, m) {
                    window.status = oldstatus;
                    unauthorizedRedirect(e);
                    if (onError != null) onError(e);
                },
            success: function () {
                window.status = oldstatus;
                if (onModelUpdated != null) onModelUpdated(me);
            }
        });

    }

    this.deleteFromModelSource = function (args) {

        if (args == null)
            args = new Object();

        var onModelDeleted = args['onModelDeleted'];
        var onError = args['onError'];
        var datacontext = _parsedModelSrc(this, args['datacontext']);

        if (datacontext == null) {
            datacontext = this.parsedModelSrc();
            if (datacontext == null)
                throw 'Could not found model source for remove model.';
        }

        // gets the parentVm
        var parentVm = Rajon.getParentViewModel(this.getView());
        var itemVm = this;

        var oldstatus = window.status;
        window.status = 'updating data...';

        $.ajax({ type: "DELETE",
            url: datacontext,
            contentType: "application/json; charset=utf-8", dataType: "text",
            error: function (e, m) {
                window.status = oldstatus;
                unauthorizedRedirect(e);
                onError(e);
            },
            success:
                function () {
                    window.status = oldstatus;
                    if (parentVm.model instanceof Array) {
                        parentVm.removeItemViewModel(itemVm.model);
                        if (onModelDeleted != null)
                            onModelDeleted();
                    }
                }
        });


    }

    this.addToModelSource = function (args) {


        if (args == null)
            args = new Object();

        var onModelAdded = args['onModelAdded'];
        var onError = args['onError'];
        var datacontext = _parsedModelSrc(this, args['datacontext']);

        if (datacontext == null) {
            datacontext = this.parsedModelSrc();
            if (datacontext == null)
                throw 'Could not found model source to add model.';
        }

        // commit changes to the model
        if (this.getView() != null)
            this.commit();

        if (this.model == null)
            return;

        var jsonStr = null;
        if (Rajon.wcfTarget)
            jsonStr = JSON.stringifyWCF(this.model);
        else
            jsonStr = JSON.stringify(this.model);

        var oldstatus = window.status;
        window.status = 'updating data...';

        $.ajax({
            type: "POST",
            url: datacontext,
            data: jsonStr,
            contentType: "application/json; charset=utf-8", dataType: "text",
            error:
                function (e, m) {
                    window.status = oldstatus;
                    unauthorizedRedirect(e);
                    onError(e);
                },
            success:
                function (data) {
                    window.status = oldstatus;
                    if (onModelAdded != null)
                        onModelAdded(data);
                }
        });
    }

    //#endregion

    //#region ITEMS VMODELS METHODS  //////////////////////////////////////////////////

    var itemsViewModels = new Array();

    createItemViewElement = function (view, html, addNewItemsOn) {

        if (addNewItemsOn == null)
            addNewItemsOn = view.getAttribute('addNewItemsOn');

        var item = $(html);
        if (addNewItemsOn == 'top')
            $(view).prepend(item);
        else
            $(view).append(item);

        return item[0];
    }


    this.clearItemsViewModel = function () {
        for (var i = itemsViewModels.length - 1; i >= 0; i--) {
            var view = itemsViewModels[i].getView();
            var removed = view.parentNode.removeChild(view);
        }
        itemsViewModels = new Array();
    }

    this.addItemViewModel = function (model, addNewItemsOn) {

        var itemView = createItemViewElement(this.getView(), this.itemView, addNewItemsOn);
        var itemvModel = Rajon.createViewModel(itemView);
        itemvModel.model = model;

        if (!(this.model instanceof Array)) {
            this.model = new Array();
        }

        this.model.push(itemvModel.model);

        var l = itemsViewModels.push(itemvModel);
        if (itemvModel.model != null)
            itemvModel.model._modelIndex = l - 1;

        return itemvModel;
    }

    this.getItemViewModel = function (model) {
        if (model._modelIndex == null)
            return null;
        return itemsViewModels[model._modelIndex];
    }


    this.removeItemViewModel = function (modelToRemove) {

        // remove from view and viewmodel
        if (modelToRemove._modelIndex < 0)
            return;
        var view = itemsViewModels[modelToRemove._modelIndex].getView();
        view.parentNode.removeChild(view);
        itemsViewModels.splice(modelToRemove._modelIndex, 1);

        // remove from model
        for (var i = modelToRemove._modelIndex + 1, li = this.model.length; i < li; i++)
            this.model[i]._modelIndex = this.model[i]._modelIndex - 1;
        this.model.splice(modelToRemove._modelIndex, 1);

        return;
    }


    //#endregion


    this.parsedModelSrc = function () {
        return _parsedModelSrc(this, this.datacontext);
    }

    _parsedModelSrc = function (me, orgSrc) {
        if (orgSrc == null || orgSrc[0] == '=')
            return orgSrc;

        // parse request values
        var src = orgSrc;
        var regex = /{request\.(.*?)}/g;
        var results;
        while (results = regex.exec(orgSrc)) {
            src = src.replace('{request.' + results[1] + '}', Rajon.request(results[1]));
        }

        // parse anchor values
        orgSrc = src;
        var regex = /{anchor\.(.*?)}/g;
        var results;
        while (results = regex.exec(orgSrc)) {
            src = src.replace('{anchor.' + results[1] + '}', Rajon.anchor(results[1]));
        }

        // parse document values
        orgSrc = src;
        regex = /{document\.(.*?)}/g;
        var results;
        while (results = regex.exec(orgSrc)) {
            if (results[1].indexOf('.') > 0) {
                var elementId = results[1].substr(0, results[1].indexOf('.'));
                var prop = results[1].substr(results[1].indexOf('.'));
                var element = document.getElementById(elementId);

                var castSuffix = '';
                if (prop.indexOf('#') > 0) {
                    castSuffix = prop.substr(prop.indexOf('#'));
                    prop = prop.replace(castSuffix, '');
                }

                var value = eval('element' + prop);
                value = Rajon.convertForQueryString(castSuffix, value);
                src = src.replace('{document.' + results[1] + '}', encodeURI(value));
            }
        }

        // parse vmodel values
        orgSrc = src;
        regex = /{vmodel\.(.*?)}/g;
        var results;
        while (results = regex.exec(orgSrc)) {
            //var model = me.model;
            //if (model == null) 
            model = Rajon.getParentViewModel(me.getView()).model;

            src = src.replace('{vmodel.' + results[1] + '}', eval('model.' + results[1]));
        }

        // if pagining always use cache off
        if (me.pageSize > 0 || me.restCache == 'no-cache')
            src = addParameter(src, 'cacheStamp=' + new Date().getTime());
        else if (me.restCache == 'session')
            src = addParameter(src, 'cacheStamp=' + Rajon.cacheSessionStamp);
        else if (me.restCache != 'browser')
            src = addParameter(src, 'cacheStamp=' + Rajon.cachePageStamp);

        return src;
    }

    addParameter = function (query, parameter) {
        if (query.indexOf('?') > 0)
            return query + '&' + parameter;
        else
            return query + '?' + parameter;
    }



    this.getModelFromServer = function (onModelRetrived) {

        this.newModel = null;

        // if the datacontext is null, return        
        if (this.datacontext == null)
            return;

        // if the datacontext is a REST Request
        this.gettingModel = true;

        var oldstatus = window.status;
        window.status = 'getting data...';

        var me = this;

        // gets from ajax
        $.ajax({
            type: "GET",
            beforeSend:
                function (XMLHttpRequest) {
                    if (me.pageSize > 0) {
                        XMLHttpRequest.setRequestHeader("rajonPageSize", me.pageSize);
                        XMLHttpRequest.setRequestHeader("rajonPageNumber", me.getPageNumber());
                    }
                },
            complete:
                function (XMLHttpRequest) {
                    me.itemsTotalCount = XMLHttpRequest.getResponseHeader('rajonItemsTotalCount');
                    $(me.getView()).trigger('onModelRetrived', me);
                },
            url: me.parsedModelSrc(),
            contentType: "application/json; charset=utf-8", dataType: "text",
            success:
                function (data) {
                    window.status = oldstatus;
                    me.gettingModel = false;
                    if (data != null && data != '')
                        me.newModel = JSON.parse(data);

                    if (onModelRetrived != null)
                        onModelRetrived(me);


                },
            timeout:
            function (data) {
                window.status = oldstatus;
                me.gettingModel = false;
            },
            error:
                function (data) {
                    window.status = oldstatus;
                    me.gettingModel = false;
                    unauthorizedRedirect(data);
                }
        });
    }

    unauthorizedRedirect = function (data) {
        if (data.status == 401 && Rajon.unauthorizedUrl != null)
            document.location.href = Rajon.unauthorizedUrl;
    }

    onModelSourceChange = function (me, newModel) {
        if (newModel instanceof Array) {
            me.clearItemsViewModel();
        }
    }







}


//#region RAJON COOKIE METHODS //////////////////////////////////////////////////

Rajon.setCookie = function (c_name, value, exdays) {
    var exdate = new Date();
    exdate.setDate(exdate.getDate() + exdays);
    var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
    document.cookie = c_name + "=" + c_value;
}

Rajon.getCookie = function (c_name) {
    var i, x, y, ARRcookies = document.cookie.split(";");
    for (i = 0; i < ARRcookies.length; i++) {
        x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
        y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
        x = x.replace(/^\s+|\s+$/g, "");
        if (x == c_name) {
            return unescape(y);
        }
    }
}

//#endregion



var _rajonRegDateISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/;

JSON.stringifyWCF = function (obj) {
    return JSON.stringify(obj,
        function (key, value) {
            if (typeof value == "string") {
                var a = _rajonRegDateISO.exec(value);
                if (a) {
                    var val = '/Date(' + new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6])).getTime() + ')/';
                    return val;
                }
            }
            return value;
        });
}


function Rajon() { }

Rajon.log = function (msg) {
    /// <summary>Logs a message at the js console</summary>
    /// <param name="msg" type="String">Message to be logged</param>
    setTimeout(function () {
        throw new Error(msg);
    }, 0);
}

Rajon.initLocaleSettings = function () {

    // auto detect number locale settings
    var float = 1234.5;
    var floatstr = float.toLocaleString();

    Rajon.milharSeparator = floatstr.substr(1, 1);
    Rajon.decimalSeparator = floatstr.substr(5, 1);

    // auto detect date locale settings
    // default: US style
    Rajon.dateFormatMonthIdx = 0;
    Rajon.dateFormatDayIdx = 1;
    Rajon.dateFormatYearIdx = 2;

    var date = new Date(2010, 0, 30);
    var dateStr = date.toLocaleDateString();

    var parts = dateStr.split(' ');
    if (parts.length > 1) {
        var dayOrMonth = parseInt(parts[1]);
        if (dayOrMonth) {
            Rajon.dateFormatMonthIdx = 1;
            Rajon.dateFormatDayIdx = 0;
            Rajon.dateFormatYearIdx = 2;
        }
    }



}

Rajon.request = function (name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    if (results == null) return "";
    else return results[1];
}

Rajon.anchor = function (name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regexS = "[\\#&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    if (results == null) return "";
    else return results[1];
}



Rajon.toFloat = function (str) {

    if (Rajon.decimalSeparator == '.' && Rajon.milharSeparator == ',')
        return parseFloat(str);

    var s = str;

    s = s.replace(Rajon.decimalSeparator, '#d');
    s = s.replace(Rajon.milharSeparator, '#m');
    s = s.replace('#d', '.');
    s = s.replace('#m', '.');

    return parseFloat(s);
}

Rajon.toDate = function (str) {
    try {
        if (str == null)
            return null;

        if (typeof str != "string")
            return str;

        // if is a JSON string date
        if (str.substr(0, 5) == '/Date')
            return new Date(parseInt(str.substr(6)));

        // id already at the JS format
        if (Rajon.dateFormatMonthIdx == 0 && Rajon.dateFormatMonthIdx == 1)
            return new Date(str);

        var parts = str.split('/');
        return new Date(parts[Rajon.dateFormatMonthIdx] + '/' + parts[Rajon.dateFormatDayIdx] + '/' + parts[Rajon.dateFormatYearIdx]);
    }
    catch (e) {
        Rajon.log('Rajon issue: error converting ' + str + ' to date.');
    }
}

Rajon.formatDate = function (d) {
    if (d == null)
        return "";

    if (typeof d == 'string')
        d = Rajon.toDate(d);

    if (Rajon.dateFormatDayIdx == 0)
        return d.getDate() + '/' + (d.getMonth() + 1) + '/' + d.getFullYear();
    else
        return (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear();
}

Rajon.toQueryStringDate = function (str) {
    // first converts it to date
    var dt = Rajon.toDate(str);
    return (dt.getMonth() + 1) + '/' + dt.getDate() + '/' + dt.getFullYear();
}

Rajon.createViewModel = function (view) {

    var vmodel = new RajonViewModel();
    var vModelClass = view.getAttribute('rajon:vModelClass');
    if (vModelClass != null)
        eval('vmodel = new ' + vModelClass + '();');
    vmodel.setView(view);

    vmodel.datacontext = view.getAttribute('rajon:datacontext');
    vmodel.restCache = view.getAttribute('rajon:restCache');
    vmodel.bindsOn = view.getAttribute('rajon:bindsOn');
    vmodel.pageSize = view.getAttribute('rajon:pageSize');
    vmodel.pageScrollSpeed = parseInt(view.getAttribute('rajon:pageScrollSpeed'));
    

    // Sets the item View
    // if is a inline itemview
    if (view.getAttribute('rajon:itemView') != null) {
        var itemView = document.getElementById(view.getAttribute('rajon:itemView'));
        if (itemView != null)
            vmodel.itemView = itemView.innerHTML;
    }
    // if is a template itemview
    else {
        // how do that in jquery?
        var childs = view.children;
        for (var i = 0, li = view.children.length; i < li; i++) {
            if (childs[i].getAttribute('rajon:isItemView') == 'true') {
                vmodel.itemView = $(childs[i]).outerHTML();
                vmodel.itemView = vmodel.itemView.replace('rajon:isitemview="true"', '');
                view.removeChild(childs[0]);
                break;
            }
        }
    }

    // set commitOnLostFocus            
    $('[rajon\\:commitOnLostFocus="true"]', view).blur(
                function () {
                    var vm = Rajon.vmodel(this);
                    vm.commitTag(this);
                });

    return vmodel;
}

Rajon.getParentViewModel = function (view) {
    var parent = $(view).parent()[0];
    if (parent == null || parent == document)
        return null;
    if (parent.vmodel != null || parent.getAttribute("rajon:datacontext") != null || parent.getAttribute("rajon:vModelClass") != null)
        return parent.vmodel;
    return Rajon.getParentViewModel(parent);
}


Rajon.vmodel = function (viewIdOrTag) {
    var view = viewIdOrTag;
    if (viewIdOrTag.nodeName == null)
        view = document.getElementById(viewIdOrTag);

    if (view.vmodel == null)
        return Rajon.getParentViewModel(view);

    return view.vmodel;
}

Rajon.loadLocaleResources = function () {
    Rajon.Resources = new Object();
    Rajon.Locale = $('meta[name=rajonLocale]').attr("content");

    if (Rajon.Locale == null)
        return;

    var resourceFiles = new Array();
    var useResources = $('meta[name=rajonUseResources]').attr("content");
    if (useResources == null)
        resourceFiles[0] = 'res';
    else
        resourceFiles = useResources.replace(' ', '').split(',');

    var ext = '.js';

    if (Rajon.Locale == 'client') {
        ext = navigator.browserLanguage;
        if (ext == null)  // ff browser
            ext = navigator.language;
        ext = '.' + ext + '.js';
    }

    for (var i = 0, li = resourceFiles.length; i < li; i++)
        $.getScript('resources/' + resourceFiles[i] + ext);

}

Rajon.getFromView = function (view, attr, result) {
    var attrs = attr.split(',');
    for (var i = 0, li = view.childNodes.length; i < li; i++) {
        var subView = view.childNodes[i];
        // dont want get plan text
        if (subView.nodeType != 3) {
            for (var ai = 0, ali = attrs.length; ai < ali; ai++)
                if (subView.getAttribute(attrs[ai]) != null)
                    result.push(subView);
            if (subView.getAttribute('rajon:datacontext') == null)
                Rajon.getFromView(subView, attr, result);
        }
    }
}

Rajon.registerConverter = function (sufix, className) {
    if (Rajon.converters == null)
        Rajon.converters = new Object();
    Rajon.converters['#' + sufix] = className;
}

Rajon.convert = function (sufix, value) {
    if (Rajon.converters[sufix] == null)
        return value;
    if (eval(Rajon.converters[sufix])['convert'] == null)
        return value;
    return eval(Rajon.converters[sufix] + '.convert(value)');
}

Rajon.convertBack = function (sufix, value) {
    if (Rajon.converters[sufix] == null)
        return value;
    if (eval(Rajon.converters[sufix])['convertBack'] == null)
        return value;
    return eval(Rajon.converters[sufix] + '.convertBack(value)');
}

Rajon.convertForQueryString = function (sufix, value) {
    if (Rajon.converters[sufix] == null)
        return value;
    if (eval(Rajon.converters[sufix])['convertForQueryString'] == null)
        return value;
    return eval(Rajon.converters[sufix] + '.convertForQueryString(value)');
}  

Rajon.onRajonInit = function (func) {
    $('body').bind('onRajonInit', func);
}



Rajon.init = function () {

    Rajon.wcfTarget = true;
    Rajon.initLocaleSettings();
    Rajon.loadLocaleResources();

    Rajon.registerConverter('d', 'DateConverter');
    Rajon.registerConverter('i', 'IntegerConverter');
    Rajon.registerConverter('f', 'FloatConverter');

    Rajon.unauthorizedUrl = $('meta[name=rajonUnauthorizedUrl]').attr("content");

    Rajon.cachePageStamp = new Date().getTime();

    Rajon.cacheSessionStamp = Rajon.getCookie('rajonCacheSStamp');
    if (Rajon.cacheSessionStamp == null) {
        Rajon.cacheSessionStamp = new Date().getTime();
        Rajon.setCookie('rajonCacheSStamp', Rajon.cacheSessionStamp);
    }

    // for all views with vmodel            
    var views = $('[rajon\\:datacontext]');
    var li = views.length;

    // create the view models
    for (var i = 0; i < li; i++)
        Rajon.createViewModel(views[i]);

    // bind the datacontexts
    for (var i = 0; i < li; i++) {
        // only the onload bind                
        if (views[i].vmodel.bindsOn == null || views[i].vmodel.bindsOn == 'onLoad') {
            // only server datacontexts
            if (views[i].vmodel.isSERVERDataContext())
                views[i].vmodel.bind();
            // or any datacontext that has no parent
            else if (Rajon.getParentViewModel(views[i]) == null)
                views[i].vmodel.bind();
        }


    }

    // triggers the onRajonInit event
    $('body').trigger('onRajonInit');

}



$(document).ready(function () { Rajon.init(); });

